import urllib.parse
import requests
import datetime

from coin.base.datetime_util import to_timestamp_int


class FinschiaNativePrivateClient:
  def __init__(self, *, node_info, api_key=None, timeout=60):
    self._host = node_info
    self._api_key = api_key
    self._timeout = timeout

  def _get(self, host, path, params=None):
    url = urllib.parse.urljoin(host, path)
    response = requests.request('GET', url, params=params, timeout=self._timeout)
    response.raise_for_status()
    content = response.json()
    return content

  def get_latest_balance(self, address, token_address):
    balance_response = self._get(self._host, f'cosmos/bank/v1beta1/balances/{address}')
    balances = balance_response['balances']
    balance = next((balance['amount'] for balance in balances if balance['denom'] == token_address), 0)
    delegation_response = self._get(self._host, f'cosmos/staking/v1beta1/delegations/{address}')
    delegations = delegation_response['delegation_responses']
    delegation = next((delegation['balance']['amount'] for delegation in delegations
                      if delegation['balance']['denom'] == token_address), 0)
    reward_response = self._get(self._host, f'cosmos/distribution/v1beta1/delegators/{address}/rewards')
    rewards = reward_response['total']
    reward = next((reward['amount'] for reward in rewards if reward['denom'] == token_address), 0)
    balance = float(balance) + float(delegation) + float(reward)
    return balance

  def get_token_balance(self, address, token_address, end_height=None):
    balance = self.get_latest_balance(address, token_address)
    if end_height is not None:
      end_timestamp = self.get_block_timestamp(end_height)
      latest_timestamp = self.get_latest_block_number_timestamp()[1]
      transactions_to_adjust = self.get_token_transactions(
        address, token_address, end_timestamp, latest_timestamp)
      balance -= transactions_to_adjust
    return balance

  def get_token_transactions(self, address, token_address, start_timestamp, end_timestamp):
    transact_info = self._get(self._host, f'cosmos/tx/v1beta1/txs?events=message.sender=%27{address}%27')
    raw_txs = 0
    txs = transact_info.get('txs', [])
    tx_responses = transact_info.get('tx_responses', [])
    for idx in range(len(txs)):
      blcok_timestamp = self.get_block_timestamp(tx_responses[idx]['height'])
      if blcok_timestamp > start_timestamp and blcok_timestamp <= end_timestamp:
        fees = txs[idx]['auth_info']['fee']['amount']
        fee = float(next((fee['amount'] for fee in fees if fee['denom'] == token_address), 0))
        for tx_msg in txs[idx]['body']['messages']:
          if 'from_address' in tx_msg or 'to_address' in tx_msg:
            amount = float(next((amount['amount'] for amount in tx_msg['amount']
                                 if amount['denom'] == token_address), 0))
            if address == tx_msg['from_address']:
              raw_txs = raw_txs - amount - fee
            elif address == tx_msg['to_address']:
              raw_txs += amount
    return raw_txs

  def get_latest_block_number_timestamp(self):
    block_info = self._get(self._host, 'lbm/base/ostracon/v1/blocks/latest')
    block_height = int(block_info['block']['header']['height'])
    block_timestamp = datetime.datetime.strptime(
      block_info['block']['header']['time'][:-6], "%Y-%m-%dT%H:%M:%S.%f")
    return (block_height, to_timestamp_int(block_timestamp))

  def get_block_timestamp(self, block_number):
    block_info = self._get(self._host, f'lbm/base/ostracon/v1/blocks/{block_number}')
    block_timestamp = datetime.datetime.strptime(
          block_info['block']['header']['time'][:-6], "%Y-%m-%dT%H:%M:%S.%f")
    return to_timestamp_int(block_timestamp)
